<div id="app">{{title}}</div>

<script>
  // 填充代码
  // 1. 基本结构
  const Vue = {
    // 拓展性
    createRenderer({
      querySelector,
      insert
    }) {
      // 返回渲染器
      return {
        createApp(options) {
          // 返回app对象
          return {
            mount(selector) {
              // 1. 找到宿主元素
              const parent = querySelector(selector)
              // 2. 渲染页面
              if (!options.render) {
                // 2.1 处理template： 编译
                options.render = this.compile(parent.innerHTML)
              }

              // 收集 setup 和其他选项
              if (options.setup) {
                this.setupState = options.setup()
              }
              if (options.data) {
                this.data = options.data()
              }

              // 渲染之前，处理setup和其他选项的兼容
              const proxy = new Proxy(this, {
                get(target, key) {
                  // 先从setup中取，如果取不到再从data中取
                  // 如果setup存在，且key在setup中定义过
                  if (target.setupState && key in target.setupState) {
                    // return target.setupState[key]
                    return Reflect.get(target.setupState, key)
                  } else {
                    // return target.data[key]
                    return Reflect.get(target.data, key)
                  }

                },
                set(target, key, val) {
                  if (target.setupState && key in target.setupState) {
                    return Reflect.set(target.setupState, key, val)
                  } else {
                    return Reflect.set(target.data, key, val)
                  }
                }
              })


              // 2.2 用户直接编写render
              this.update = effect(() => {
                // 执行render函数（这里指定render函数中的this的上下文，是配置项中data函数的返回值）
                const el = options.render.call(proxy)

                // 3. 追加到宿主
                insert(el, parent)
              })
              // this.update()


            },
            compile(template) {
              // 返回一个render函数
              // parse -> ast
              // generate: ast -> render
              return function render() {
                const h3 = document.createElement('h3')
                h3.textContent = this.title
                return h3
              }
            }
          }
        }
      }
    },
    createApp(options) {
      // 创建一个web平台特有的渲染器
      const renderer = Vue.createRenderer({
        querySelector(sel) {
          return document.querySelector(sel)
        },
        insert(el, parent) {
          parent.innerHTML = ''
          parent.appendChild(el)
        }
      })
      return renderer.createApp(options)
    }

  }
</script>

<script>
  // 内容拦截用户对代理对象的访问，从而在值发生变化的时候做出响应
  function reactive(obj) {
    // 返回代理的对象
    return new Proxy(obj, {
      get(target, key) {
        console.log('get key:', key)
        // 建立依赖关系
        track(target, key)
        return Reflect.get(target, key)
      },
      set(target, key, val) {
        console.log('set key:', key)
        Reflect.set(target, key, val)
        // 获取依赖关系
        // 通知更新
        // app.update()
        // 触发更新
        trigger(target, key) 
      }
    })
  }

  // 建立映射关系： 依赖 Dep -> 组件更新函数


  // 调用effect，首先执行fn
  // 临时存储副作用函数 effectStack
  const effectStack = []

  function effect(fn) {
    // 1. 执行一次fn
    // fn()
    const eff = function () {
      try {
        effectStack.push(eff)
        fn()
      } finally {
        effectStack.pop()
      }
    }
    // 立即调用一次
    eff()
    // 将这个函数返回出去
    return eff
  }

  // Vue3: 创建map结构 { target: {key: [update1, update2]} }
  // const targetMap = {
  //   // 应该往里面存这样的数据，依赖关系
  //   // state: {
  //   //   'title': [update]
  //   // }
  // }
  const targetMap = new WeakMap()
  // const targetMap = {}
  // 建立 target,key 和 effectStack 中存储的副作用函数之间的关系
  function track(target, key) {
    // 拿出存储副作用函数的最后一个元素
    const effect = effectStack[effectStack.length - 1]
    // 写死的话是这样的，但是不能这样写，要不然每次都新创建一个对象
    // targetMap[target] = {}
    // targetMap[target][key] = [effect]

    // 所以应该先判断target为key的对象存不存在
    // let map = targetMap[target]
    let map = targetMap.get(target)
    if (!map) {
      // 首次get这个target【不存在就给map初始化一下】 
      // map = targetMap[target] = {}
      map = targetMap.set(target, {})
    }

    let deps = map[key]

    if (!deps) {
      // deps = map[key] = []
      deps = map[key] = new Set()
    }
    // 映射关系建立
    // if (deps.indexOf(effect) === -1) {
    //   deps.push(effect)
    // }
    deps.add(effect)

    console.log(targetMap)
    console.log(deps)
  }

  function trigger(target, key) {
    // const map = targetMap[target]
    const map = targetMap.get(target)
    console.log(targetMap)
    console.log(map)

    if (map) {
      // console.log(map)
      const deps = map[key]
      // console.log(typeof deps)
      if (deps) {
        for (let dep of deps) {
          dep()
        }
      }
      // if (deps) {
      //   deps.forEach(dep => dep())
      // }
    }
  }

  // const obj = reactive({
  //   foo: 'foo'
  // })
  // effect(() => {
  //   // 触发响应式数据 
  //   console.log(obj.foo)
  // })
  // obj.foo = 'foo改变了~~~'
</script>


<script>
  const app = Vue.createApp({
    setup() {
      const state = reactive({
        title: 'hello, vue3 YK菌'
      })

      setTimeout(() => {
        state.title = '2秒后见到新的YK菌'
      }, 2000)

      return state
    }
  })
  app.mount('#app')
</script>